/*
    This source file is part of GeNa project.
    Copyright © GeNa project 2008
    For the latest info, see http://code.google.com/p/genaproject/ or  http://lumpyproject.forums-rpg.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Foobar is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. */

#include <cfloat>

#include <dae.h>
#include <dom/domCOLLADA.h>

#include "model.h"
#include "unique_name_generator.h"
#include "distance.h"

#include "effect.h"

void Model::load(Map* map)
{
    entity = map->modelManager->load(name);
}


class GetSqDistanceFromAxis //Functor
{
private:
    SubMesh* subMesh;
    VertexElement* posElem;

    int getIndice( int i, void* ptr, int size)
    {
        int s = size;

        //Fill “size” first bytes of mask, a few bit twiddling !
        int mask = -(size & 1) & 0xff; //Should be at least one byte

        size >>= 1;
        register int t = -(size & 1);
        mask <<= t & 16;
        mask |= t & 0xffff;

        size >>= 1; //Case where size = 4
        mask |= -(size & 1) & 0xffffffff;

        //That’s all !
        return *(int*)((unsigned int)ptr + i*s) & mask;
    }

    template<class T> math::vector3f getVectorT(int i, void* ptr)
    {
        T* p = posElem->get<T>(i, ptr);

        return math::vector3f(   (float)p[0],
                                posElem->numComponents < 2 ? 0 : (float)p[1],  //0 if  posElem->numComponents < 2 else p[1]
                                 posElem->numComponents < 3 ? 0 : (float)p[2]); //0 if  posElem->numComponents < 3 else p[2]
    }

     math::vector3f getVector(int i, void* ptr)
     {
         switch( posElem->type)
         {
             case GL_SHORT:
                return getVectorT<short>(i,ptr);
             case GL_INT:
                return getVectorT<int>(i,ptr);
             case GL_FLOAT:
                return getVectorT<float>(i,ptr);
             case GL_DOUBLE:
                return getVectorT<double>(i,ptr);
            default:
                return math::vector3f();
         }
     }

public:
    GetSqDistanceFromAxis( SubMesh* _subMesh)
    {
        subMesh = _subMesh;
        posElem = subMesh->namedElements["POSITION"];
    }

    float operator()(math::vector3f p, math::vector3f v)
    {
        if( !posElem)
            return FLT_MAX;

        float dist = FLT_MAX;

        //Lock buffer

        void* iptr = subMesh->indexBuffer->map(GL_READ_ONLY );
        void* vptr = subMesh->vertexBuffers[posElem->buffer]->map(GL_READ_ONLY);

        unsigned int isize;
        switch( subMesh->indexType)
        {
            case GL_UNSIGNED_BYTE:
                isize = 1;
                break;
            case GL_UNSIGNED_SHORT:
                isize = 2;
                break;
            case GL_UNSIGNED_INT:
                isize = 4;
                break;
            default:
                isize = 0;
                break;
        }

        switch( subMesh->renderingMode)
        {
            case GL_POINTS:
                for( unsigned int i = 0; i < subMesh->count; i++)
                {
                    float d = sqDistPointToLine(p,v,getVector(getIndice(i, iptr, isize), vptr));
                    if( d < dist)
                        dist = d;
                }
                break;
            case GL_LINES:
                for( unsigned int i = 0; i <subMesh->count; i+=2)
                {
                    float d = sqDistLineToSegment(p,v,getVector(getIndice(i, iptr, isize), vptr),getVector(getIndice(i+1, iptr, isize), vptr));
                    if( d < dist)
                        dist = d;
                }
                break;
            case GL_LINE_STRIP:
                for( unsigned int i = 0; i <subMesh->count - 1; i++)
                {
                    float d = sqDistLineToSegment(p,v,getVector(getIndice(i, iptr, isize), vptr),getVector(getIndice(i+1, iptr, isize), vptr));
                    if( d < dist)
                        dist = d;
                }
                break;
            case GL_TRIANGLES:
                for( unsigned int i = 0; i <subMesh->count; i+=3)
                {
                    float d = sqDistLineToSegment(p,v,getVector(getIndice(i, iptr, isize), vptr),getVector(getIndice(i+1, iptr, isize), vptr));
                    if( d < dist)
                        dist = d;

                    d = sqDistLineToSegment(p,v,getVector(getIndice(i, iptr, isize), vptr),getVector(getIndice(i+2, iptr, isize), vptr));
                    if( d < dist)
                        dist = d;

                    d = sqDistLineToSegment(p,v,getVector(getIndice(i+1, iptr, isize), vptr),getVector(getIndice(i+2, iptr, isize), vptr));
                    if( d < dist)
                        dist = d;
                }
                break;
            case GL_TRIANGLE_STRIP:
                for( unsigned int i = 0; i <subMesh->count-2; i++)
                {
                    float d = sqDistLineToSegment(p,v,getVector(getIndice(i, iptr, isize), vptr),getVector(getIndice(i+1, iptr, isize), vptr));
                    if( d < dist)
                        dist = d;

                    d = sqDistLineToSegment(p,v,getVector(getIndice(i, iptr, isize), vptr),getVector(getIndice(i+2, iptr, isize), vptr));
                    if( d < dist)
                        dist = d;

                    d = sqDistLineToSegment(p,v,getVector(getIndice(i+1, iptr, isize), vptr),getVector(getIndice(i+2, iptr, isize), vptr));
                    if( d < dist)
                        dist = d;
                }
                break;
            case GL_TRIANGLE_FAN:
                for( unsigned int i = 0; i <subMesh->count-1; i++)
                {
                    float d = sqDistLineToSegment(p,v,getVector(getIndice(i, iptr, isize), vptr),getVector(getIndice(i+1, iptr, isize), vptr));
                    if( d < dist)
                        dist = d;

                    d = sqDistLineToSegment(p,v,getVector(getIndice(i, iptr, isize), vptr),getVector(getIndice(0, iptr, isize), vptr));
                    if( d < dist)
                        dist = d;

                    d = sqDistLineToSegment(p,v,getVector(getIndice(i+1, iptr, isize), vptr),getVector(getIndice(0, iptr, isize), vptr));
                    if( d < dist)
                        dist = d;
                }
                break;
        }

        subMesh->indexBuffer->unMap();
        subMesh->vertexBuffers[posElem->buffer]->unMap();

        return dist;
    }
};

void Model::select()
{
    isSelected = true;
}

void Model::unselect()
{
    isSelected = false;
}

#define OUT_VEC(p) std::cout << "x " << p.x << " y " << p.y << " z " << p.z << std::endl;

float Model::getSqDistanceFromAxis( math::vector3q p, math::vector3a v)
{
    //Transform p and v to local space, must be on a direct child of root node
    p -= position;
    math::vector3f pos = p;
    math::quaternionf quat = orientation.inverse();
    pos = quat.transform(pos);
    v = quat.transform(v);

    //Calculate axis

    float dist = FLT_MAX;
    if( !entity)
        return dist;

    for( std::list<SubMesh*>::iterator iter = entity->mesh->m_subMeshes.begin(); iter != entity->mesh->m_subMeshes.end(); iter++)
    {
        float d = GetSqDistanceFromAxis(*iter)(pos,v);

        if( d < dist)
            dist = d;
    }
    return dist;
}

void Model::draw(bool noEffect)
{
    if(!entity)
        return;

    if( isSelected)
        glColor3f( 1.0, 0.0, 0.0);
    else
        glColor3f( 0.0, 0.0, 0.0);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    math::matrix4f mat = orientation.toMatrix4();
    mat.m14 = position.x;
    mat.m24 = position.y;
    mat.m34 = position.z;
    glMultTransposeMatrixf(mat.l);

    entity->draw();

    glPopMatrix();
}

Entity* ModelManager::load( std::string name)
{
    Entity* entity = entityList[name];

    if( entity)
        return entity;

    //Load mesh
    DAE dae;
    domCOLLADA* root = (domCOLLADA*)dae.open(name);
    if (!root)
    {
        cout << "Document import failed.\n";
        return NULL;
    }

    std::vector<domInstance_geometry*> instances = dae.getDatabase()->typeLookup<domInstance_geometry>();
    entity = new Entity(instances[0]);

    entityList[name] = entity;
    return entity;
}

